/*
 * Copyright (C) 2014 Hortator
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package engine.net.chat;

import engine.net.chat.Chat.ChatMessage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Collection;
import util.Logger;

/**
 * A chat server for a EmergenceEngine chat.
 *
 * @author Nathan Templon
 */
public class ChatServer extends Thread {

    // Logs what is going on
    private static final Logger logger = Logger.getDefaultLogger();

    // How many servers have been created - this generates a unique name for each thread
    private static int serverNumber = 0;

    private boolean running;

    private final String threadName; // The name of the thread for this server
    private ServerSocket server; // The server socket that accepts clients
    private int numSocketsCreated = 0; // The number of sockets created - guarantees unique threads for each client listener
    private final int portNumber; // The port number for this server

    private final Collection<ChatServerThread> threads; // The differend threads that are listening to clients for this server

    /**
     * Standard constructor.
     *
     * @param port the port number to run the server on
     */
    public ChatServer(int port) {
        super("Chat Server " + serverNumber);
        this.threads = new ArrayList<>();
        threadName = "Chat Server " + serverNumber;
        serverNumber++;

        this.portNumber = port;
    }

    /**
     * This method is called to run the server.
     */
    @Override
    public void run() {
        running = true;
        try {
            server = new ServerSocket(portNumber);
            new SocketListenerThread().start();
        }
        catch (IOException ex) {
            logger.log(ex);
        }
    }

    /**
     * Closes the server and ends all connections.
     */
    public synchronized void close() {
        running = false;
    }

    /**
     * This method accepts a chat message from a thread, and sends out the chat
     * message to every connected client other than the one that originated the
     * message.
     *
     * @param thread  the thread from which the message is received
     * @param message the message that is being sent
     */
    private void distributeChatMesssageFrom(ChatServerThread thread, ChatMessage message) {
        for (ChatServerThread curThread : threads) {
//            if (curThread != thread) {
//                curThread.transmit(message);
//            }
            curThread.transmit(message);
        }
    }

    /**
     * A class for threads that listen for new clients
     */
    private class SocketListenerThread extends Thread {

        /**
         * Default constructor
         */
        public SocketListenerThread() {
            super("Socket Listener Thread " + serverNumber);
        }

        /**
         * Primary method that runs the thread.
         */
        @Override
        public void run() {
            try {
                server.setSoTimeout(100); // This is so that every 100 ms, the thread will see if the server has been killed.
            }
            catch (SocketException ex) {
                logger.log(ex);
            }

            while (running) {
                try {
                    numSocketsCreated++; // Ensures unique thread name
                    try {
                        // Get a new client
                        ChatServerThread newThread = new ChatServerThread(threadName + " " + numSocketsCreated, server.accept());
                        threads.add(newThread);
                        newThread.start();
                    }
                    catch (SocketTimeoutException ex) {
                        // If the socket times out, keep looping
                    }
                }
                catch (IOException ex) {
                    logger.log(ex);
                }
            }
        }
    }

    /**
     * A thread that handles communications with a specific client.
     */
    private class ChatServerThread extends Thread {

        private final Socket socket; // Socket for communication

        private PrintWriter out;
        private BufferedReader in;

        /**
         * The standard constructor.
         *
         * @param threadName the name of the thread to be created
         * @param socket     the Socket object for communication with the client
         */
        public ChatServerThread(String threadName, Socket socket) {
            super(threadName);
            this.socket = socket;
        }

        /**
         * The primary method that runs the thread
         */
        @Override
        public void run() {
            try {
                socket.setSoTimeout(100);

                out = new PrintWriter(socket.getOutputStream(), true);
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                String inputLine;
                while (running) {
                    try {
                        inputLine = in.readLine();
                        if (inputLine != null) {
                            ChatMessage message = new ChatMessage(inputLine);
                            distributeChatMesssageFrom(this, message);
                        }
                    }
                    catch (SocketTimeoutException ex) {

                    }
                }
            }
            catch (IOException ex) {
                logger.log(ex);
            }
        }

        /**
         * Transmits the provided message object to the client
         *
         * @param message the message to be sent to the client.
         */
        public void transmit(ChatMessage message) {
            if (out != null) {
                out.println(message.transmitString());
            }
            else {
                logger.log("Attempted to send message \"" + message.toString() + ",\" but the PrintWriter was null.");
            }
        }

    }
}
